#include "Pen.h"
#include "GameCommander.h"
#include <assert.h>

Pen::Pen()
	: m_pD3DDevice(NULL)
	, m_hWnd(NULL)
{
	for(int i = 0;i < g_numMeshes;++i)
	{
		m_pMeshBuffer[i] = NULL;
	}
	for(int i = 0;i < g_numTextures;++i)
	{
		m_stpTextureBuffer[i].lpTexture = NULL;
	}
}

Pen::~Pen()
{
	//m_pD3DFont->Release();

	if (NULL != m_pD3DDevice)
	{

		m_pD3DDevice->Release(); 
		// it may be important later on for the
		// device's release function to be called last
	}
}

bool Pen::Initialize(HWND hWnd)
{
	m_hWnd = hWnd;
	if(!InitDevice())	
		return false;
	if(!InitFont())	
		return false;
	if(!InitMeshes())	
		return false;
	if(!InitTextures())
		return false;
	if(!m_clCharacterLight.Init(m_pD3DDevice,CHARACTER_LIGHT))
		return false;

	// setup a basic material to use and set it to the device
	ZeroMemory(&m_DefaultMaterial, sizeof(D3DMATERIAL9));
	m_DefaultMaterial.Ambient = D3DXCOLOR(0, 0, 0, 1);
	m_DefaultMaterial.Diffuse = D3DXCOLOR(0, 1, 0, 1);
	m_DefaultMaterial.Specular = D3DXCOLOR(1, .5f, 0, 1);
	m_DefaultMaterial.Power = 20;

	m_pD3DDevice->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL);

	D3DXMatrixPerspectiveFovLH(&m_matPerspective, D3DXToRadian(65), 
		SCREEN_WIDTH / SCREEN_HEIGHT, 1.0f, 1000.0f);
	m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &m_matPerspective);

	return true;
}

bool Pen::InitDevice()
{
	IDirect3D9* pD3DObject = NULL;
	pD3DObject = Direct3DCreate9(D3D_SDK_VERSION);			// create the d3dobject

	::GetClientRect(m_hWnd, &m_rClientRect);

	D3DCAPS9 D3DCaps;
	HR(pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));

	// determine what type of vertex processing to use
	DWORD dwBehaviorFlags = 0;

	if(D3DCaps.VertexProcessingCaps != 0)	
	{dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;}
	else	{dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;}	

	//set up present parameters
	m_D3DParams.hDeviceWindow			= m_hWnd;
	m_D3DParams.Windowed				= true;
	m_D3DParams.AutoDepthStencilFormat	= D3DFMT_D24S8;
	m_D3DParams.EnableAutoDepthStencil	= TRUE;
	m_D3DParams.BackBufferCount			= 1;
	m_D3DParams.BackBufferFormat		= D3DFMT_X8R8G8B8;
	m_D3DParams.BackBufferWidth			= SCREEN_WIDTH;
	m_D3DParams.BackBufferHeight		= SCREEN_HEIGHT;
	m_D3DParams.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	m_D3DParams.PresentationInterval	= D3DPRESENT_INTERVAL_DEFAULT;
	m_D3DParams.Flags					= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	m_D3DParams.MultiSampleQuality		= 0;
	m_D3DParams.MultiSampleType			= D3DMULTISAMPLE_NONE;

	// setup the d3ddevice using the d3dobject and present parameters
	HRESULT hr = pD3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, 
		dwBehaviorFlags, &m_D3DParams, &m_pD3DDevice);

	assert(hr == D3D_OK && "The device is broken");
	D3DXCreateSprite(m_pD3DDevice, &m_pSpriteDevice);

	if(m_pD3DDevice == NULL)
		return false;

	pD3DObject->Release();
	return true;
}

bool Pen::InitFont()
{	
	////Top Left box
	m_FontScreenPosition[TOPLEFT].left		= (long)(SCREEN_WIDTH*0.01f);
	m_FontScreenPosition[TOPLEFT].right		= (long)(m_FontScreenPosition[TOPLEFT].left + 100);
	m_FontScreenPosition[TOPLEFT].top		= (long)(SCREEN_HEIGHT*0.01f);
	m_FontScreenPosition[TOPLEFT].bottom	= (long)(m_FontScreenPosition[TOPLEFT].top + 50);

	////Top Right box
	m_FontScreenPosition[TOPRIGHT].left		= (long)(SCREEN_WIDTH*0.80f);
	m_FontScreenPosition[TOPRIGHT].right	= (long)(m_FontScreenPosition[TOPRIGHT].left - 100);
	m_FontScreenPosition[TOPRIGHT].top		= (long)(SCREEN_HEIGHT*0.01f);
	m_FontScreenPosition[TOPRIGHT].bottom	= (long)(m_FontScreenPosition[TOPRIGHT].top + 50);

	HR(D3DXCreateFont(m_pD3DDevice, 24, 0, NULL, 0, false,
		DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE, TEXT("Times New Roman"),
		&m_pD3DFont));

	for(int i = 0;i < SCREENPOSITIONS;++i)
	{
		if(m_FontScreenPosition[i].top		== NULL &&
		   m_FontScreenPosition[i].bottom	== NULL &&
		   m_FontScreenPosition[i].right	== NULL &&
		   m_FontScreenPosition[i].left		== NULL)
			return false;
	}
	if(m_pD3DFont == NULL)
		return false;

	m_TextBuffer << "Initializing...";

	return true;
}

bool Pen::InitMeshes()
{
	m_cszMeshNameBuffer[WALL_MESH] = "Resources/Wall.X";
	m_cszMeshNameBuffer[CHARACTER_MESH] = "Resources/Character.X";
	m_cszMeshNameBuffer[ENEMY_MESH] = "Resources/Enemy.X";
	m_cszMeshNameBuffer[COIN_MESH] = "Resources/Coin.X";
	m_cszMeshNameBuffer[FILLER_MESH] = "Resources/Filler Cube.X";

	for(int i = 0; i < g_numMeshes; i++)
	{
		LoadMeshFile(m_cszMeshNameBuffer[i], m_pMeshBuffer[i]);
		if(m_pMeshBuffer[i] == NULL)
			return false;
	}

	return true;	
}

bool Pen::InitTextures()
{
	m_cszTextureNameBuffer[GREEN_TEXTURE] = "Resources/Brick.png";
	m_cszTextureNameBuffer[YELLOW_TEXTURE] = "Resources/Coin Texture.png";
	m_cszTextureNameBuffer[RED_TEXTURE] = "Resources/Enemy Texture.png";
	m_cszTextureNameBuffer[PLAYER_TEXTURE] = "Resources/Player Texture.png";
	m_cszTextureNameBuffer[MENU_TEXTURE] = "Resources/Menu.png";
	m_cszTextureNameBuffer[PRESSED_BUTTON_ONE] = "Resources/MenuButtonOne.png";
	m_cszTextureNameBuffer[PRESSED_BUTTON_TWO] = "Resources/MenuButtonTwo.png";
	m_cszTextureNameBuffer[PRESSED_BUTTON_THREE] = "Resources/MenuButtonThree.png";

	for(int i = 0; i < g_numTextures; i++)
	{
		D3DXCreateTextureFromFileEx(m_pD3DDevice, m_cszTextureNameBuffer[i], 0, 0, 0, 0, 
			D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT,
			D3DX_DEFAULT, D3DCOLOR_XRGB(150, 150, 150),
			&m_stpTextureBuffer[i].imgInfo, 0, &m_stpTextureBuffer[i].lpTexture);

		if(m_stpTextureBuffer[i].lpTexture == NULL)
			return false;
	}

	return true;
}

void Pen::LoadMeshFile(const char* cszXFile, ID3DXMesh*& pMesh)
{
	///* Empty the mesh if it is not empty */

	if (NULL != pMesh)
	{
		pMesh->Release();
	}
	pMesh = NULL;

	/* Prepare buffers returned by D3DXLoadMeshFromX */
	ID3DXBuffer* pAdjacency = NULL;			// required for OptimizeInPlace
	ID3DXBuffer* pMaterials = NULL;			// materials for each subset
	ID3DXBuffer* pEffectsInstances = NULL;	// not interested in effects now
	DWORD dwNumMaterials = 0;

	/* Load the file into the mesh */
	//HR(
	D3DXLoadMeshFromX(cszXFile, D3DXMESH_MANAGED, m_pD3DDevice, &pAdjacency, &pMaterials, &pEffectsInstances, &dwNumMaterials, &pMesh);//);
	DWORD dError = ::GetLastError();

	/* What is the flexible vertex format of the vertex buffer before cloning? */
	DWORD dMeshFVF = pMesh->GetFVF();

	/* Clone the mesh, ensuring that it has room for position, normals, and texture coordinates */
	ID3DXMesh* pClone = NULL;
	HR(pMesh->CloneMeshFVF(D3DPOOL_MANAGED, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2, m_pD3DDevice, &pClone));
	pMesh->Release();
	pMesh = pClone;

	/* Recompute the normals if they were not in the file */
	if (0 == (dMeshFVF & D3DFVF_NORMAL))
	{
		HR(D3DXComputeNormals(pMesh, (DWORD*)pAdjacency->GetBufferPointer()));
	}

	/* Optimize the mesh */
	HR(pMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
		(DWORD*)pAdjacency->GetBufferPointer(),
		0, 0, 0));

	/* Done with buffers */
	pAdjacency->Release();
	pEffectsInstances->Release();
	pMaterials->Release();
}

ID3DXMesh* Pen::getMeshByIndex(unsigned index)
{
	return m_pMeshBuffer[index];
}

LPDIRECT3DTEXTURE9 Pen::getTextureByIndex(unsigned index)
{
	return m_stpTextureBuffer[index].lpTexture;
}

bool Pen::Draw(DrawList* drawlist3D,DrawList* drawlistSprite)
{
	m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.0f), 1.0f, 0);

	HR(m_pD3DDevice->BeginScene());
	
	Render3D(drawlist3D);

	RenderSprites(drawlistSprite);

	if(m_bRenderCredits)
		RenderCredits();
	
#ifdef DEBUGGING
	RenderFont(m_TextBuffer.str());
	RenderDebugFont();
	COORD m_Pos = Cursor::Engine()->Find();
	std::stringstream out;out << "x: " << m_Pos.X << " y:" << m_Pos.Y;
	string s;s = out.str();
	Pen::Engine()->DebugFont(s);
#endif

	HR(m_pD3DDevice->EndScene());

	HR(m_pD3DDevice->Present(NULL, NULL, NULL, NULL));

	return true;
}

bool Pen::Render3D(DrawList* drawables)
{
	for(int i = 0;i < drawables->getSize();++i)
	{
		// prepare matrices that the world matrix for each object shall be composed of
		D3DXMatrixScaling(&m_matScale, drawables->get(i)->m_fScale,
			drawables->get(i)->m_fScale,
			drawables->get(i)->m_fScale);

		D3DXMatrixRotationYawPitchRoll(&m_matRotate, drawables->get(i)->m_stRotation.yaw,
			drawables->get(i)->m_stRotation.pitch,
			drawables->get(i)->m_stRotation.roll);

		D3DXMatrixTranslation(&m_matTranslate,
			drawables->get(i)->m_vPosition.x,
			drawables->get(i)->m_vPosition.y,
			drawables->get(i)->m_vPosition.z);

		// multiply the above three to create the world matrix
		D3DXMatrixMultiply(&m_matWorld, &m_matScale, &m_matRotate);
		D3DXMatrixMultiply(&m_matWorld, &m_matWorld, &m_matTranslate);

		m_pD3DDevice->SetTransform(D3DTS_WORLD, &m_matWorld);

#ifdef DEBUGGING
		if(drawables->get(i)->m_pTexture == NULL)
		{
			MessageBox(0, "Pointer to texture is trying to be accessed, but is pointing to NULL. Exiting program...",
				"Error", 0);
			return false;
		}
		if(drawables->get(i)->m_Mesh == NULL)
		{
			MessageBox(0, "Pointer to mesh is trying to be accessed, but is pointing to NULL. Exiting program...",
				"Error", 0);
			return false;
		}
#endif

		m_pD3DDevice->SetMaterial(&m_DefaultMaterial);
		m_pD3DDevice->SetTexture(0, drawables->get(i)->m_pTexture);
		m_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
		
		drawables->get(i)->Draw();
	}

	D3DXMatrixLookAtLH(&m_matView, &GAME::Engine()->Player()->CameraMan.EyePos,
									&GAME::Engine()->Player()->CameraMan.LookAt,
									&GAME::Engine()->Player()->CameraMan.UpVec);
	m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_matView);

	m_clCharacterLight.TurnUp(GAME::Engine()->Player()->m_LightRange);
	m_clCharacterLight.MoveLight(GAME::Engine()->Player()->CameraMan.EyePos);
	m_clCharacterLight.SetLight(m_pD3DDevice,CHARACTER_LIGHT);

	return true;
}

bool Pen::RenderSprites(DrawList* drawables)
{
	m_pSpriteDevice->Begin(D3DXSPRITE_ALPHABLEND);

	static int TIMER = 0;
	for(int i = 0;i < drawables->getSize();++i)
	{
		switch(drawables->get(i)->m_Type)
		{
		case MENU_TYPE:			
			m_pSpriteDevice->SetTransform(&GAME::Engine()->Sidebar()->m_Translate);
			
			//Is used to cause the "clicked" effect on buttons
			// flips between textures after a short amount of time
			if(GAME::Engine()->Sidebar()->ButtonPressed() || TIMER > 0)
			{
				if(TIMER == 0)
				{
					TIMER = 10;
				}
				--TIMER;
			}
			GAME::Engine()->Sidebar()->SetMenuState();
			if(TIMER == 0)
				GAME::Engine()->Sidebar()->Reset();

			
			m_pSpriteDevice->Draw(GAME::Engine()->Sidebar()->m_pAllMenuStates[GAME::Engine()->Sidebar()->GetState()], 0,
				&GAME::Engine()->Sidebar()->m_vCenter[GAME::Engine()->Sidebar()->GetState()], 0, GAME::Engine()->Sidebar()->m_Color);
			
			D3DXMatrixIdentity(&m_matTranslate);
			break;
		default:
			m_pSpriteDevice->Draw(drawables->get(i)->m_pTexture,0,0,0,D3DXCOLOR(255,255,255,255));
			break;
		}		
	}

	m_pSpriteDevice->End();

	if(!GAME::Engine()->Sidebar() == NULL) {
		for(int i = 0;i < 5;++i) {
			RenderMenuFont(GAME::Engine()->Sidebar()->m_Displays[i],GAME::Engine()->Sidebar()->m_Buffers[i].str().c_str());
		}
	}

	return true;
}

#ifdef _DEBUGGING
void Pen::RenderFont(string DisplayData)
{	
	m_TextBuffer << "Running...";
	if(!DisplayData.empty())
		m_TextBuffer << DisplayData;

	m_pD3DFont->DrawTextA
		(NULL,m_TextBuffer.str().c_str(),
		m_TextBuffer.str().size(),&m_FontScreenPosition[TOPLEFT],
		DT_LEFT | DT_NOCLIP,D3DCOLOR_ARGB(255, 255, 255, 255));
	m_TextBuffer.str("");
	m_TextBuffer.clear();
}
#endif

void Pen::RenderMenuFont(RECT Pos,LPCSTR Str)
{
	m_pD3DFont->DrawTextA(NULL,Str,-1,&Pos,	DT_LEFT | DT_NOCLIP,D3DCOLOR_ARGB(255, 255, 255, 255));
}

void Pen::RenderCredits()
{
	LPCSTR	Str;
	Str = "Developed by: Brandon L. York\nArt: Brandon L. York\nI think you get the idea.";
	RECT	Pos;
	Pos.top = SCREEN_HEIGHT / 2;
	Pos.left = SCREEN_WIDTH / 2 - 150;
	m_pD3DFont->DrawTextA(NULL,Str,-1,&Pos,	DT_LEFT | DT_NOCLIP,D3DCOLOR_ARGB(255, 255, 255, 255));
	static int TIMER = 0;
	if(TIMER == 0)
		TIMER = 200;
	if(TIMER > 0)
	{
		--TIMER;
		if(TIMER == 0)
			EndCredits();
	}
}

void Pen::StartCredits()
{
	m_bRenderCredits = true;
}

void Pen::EndCredits()
{
	m_bRenderCredits = false;
}

#ifdef _DEBUGGING
void Pen::RenderDebugFont()
{
	m_pD3DFont->DrawTextA
		(NULL,m_DebugTextBuffer.str().c_str(),
		m_DebugTextBuffer.str().size(),&m_FontScreenPosition[TOPRIGHT],
		DT_LEFT | DT_NOCLIP,D3DCOLOR_ARGB(255, 255, 255, 255));

	m_DebugTextBuffer.str("");
	m_DebugTextBuffer.clear();
}
#endif

void Pen::CheckDevice()
{
	// Watch for our window being covered, minimized or otherwise deactivated
	if (NULL == m_pD3DDevice){return;}							//If we have lost our D3DDevice pointer, just return
	HRESULT hDevState = m_pD3DDevice->TestCooperativeLevel();	// Check for lost device 
	switch (hDevState)
	{
	case D3DERR_DEVICELOST:		
		//If the device is lost return
		//check in the next frame
		::OutputDebugString("Device lost"); 		
		::Sleep(100);		
		return;	

	case D3DERR_DEVICENOTRESET:
		if (FAILED(m_pD3DDevice->Reset(&m_D3DParams)))
		{
			::OutputDebugString("Reset failed");
			return;
		}
		break;
	}// Done checking for deactivation and lost devices; all is well
}